#pragma once

#include <library/json/common/defs.h>

#include <util/generic/string.h>
#include <util/generic/hash.h>
#include <util/generic/vector.h>
#include <util/generic/deque.h>
#include <util/generic/yexception.h>

namespace NJson {

enum EJsonValueType {
    JSON_UNDEFINED /* "Undefined" */,
    JSON_NULL /* "Null" */,
    JSON_BOOLEAN /* "Boolean" */,
    JSON_INTEGER /* "Integer" */,
    JSON_DOUBLE /* "Double" */,
    JSON_STRING /* "String" */,
    JSON_MAP /* "Map" */,
    JSON_ARRAY /* "Array" */,
    JSON_UINTEGER /* "UInteger" */
};

class TJsonValue;

class IScanCallback {
public:
    virtual ~IScanCallback() {}

    virtual bool Do(const TString& path, TJsonValue* parent, TJsonValue& value) = 0;
};

class TJsonValue {
    void Clear() noexcept;
public:

    typedef yhash<TString, TJsonValue> TMap;
    typedef ydeque<TJsonValue> TArray;

    TJsonValue(EJsonValueType type = JSON_UNDEFINED);
    TJsonValue(bool value) noexcept;
    TJsonValue(int value) noexcept;
    TJsonValue(unsigned int value) noexcept;
    TJsonValue(long value) noexcept;
    TJsonValue(unsigned long value) noexcept;
    TJsonValue(long long value) noexcept;
    TJsonValue(unsigned long long value) noexcept;
    TJsonValue(double value) noexcept;
    TJsonValue(const TString& value);
    TJsonValue(TString&& value);
    TJsonValue(const char* value);
    TJsonValue(TStringBuf value);
    TJsonValue(const TJsonValue& vval);
    TJsonValue(TJsonValue&& vval) noexcept;

    TJsonValue& operator = (const TJsonValue& val);
    TJsonValue& operator = (TJsonValue&& val) noexcept;

    ~TJsonValue() { Clear(); }

    EJsonValueType GetType() const noexcept;
    TJsonValue& SetType(EJsonValueType type);

    TJsonValue& SetValue(const TJsonValue& value);
    TJsonValue& SetValue(TJsonValue&& value);

    // for Map
    TJsonValue& InsertValue(const TString& key, const TJsonValue& value);
    TJsonValue& InsertValue(TStringBuf key, const TJsonValue& value);
    TJsonValue& InsertValue(const char* key, const TJsonValue& value);
    TJsonValue& InsertValue(const TString& key, TJsonValue&& value);
    TJsonValue& InsertValue(TStringBuf key, TJsonValue&& value);
    TJsonValue& InsertValue(const char* key, TJsonValue&& value);

    // for Array
    TJsonValue& AppendValue(const TJsonValue& value);
    TJsonValue& AppendValue(TJsonValue&& value);
    TJsonValue& Back();

    bool GetValueByPath(TStringBuf path, TJsonValue& result, char delimiter = '.');
    bool SetValueByPath(TStringBuf path, const TJsonValue& value, char delimiter = '.');
    bool SetValueByPath(TStringBuf path, TJsonValue&& value, char delimiter = '.');

    // returns NULL on failure
    const TJsonValue* GetValueByPath(TStringBuf path, char delimiter = '.') const noexcept;
    TJsonValue* GetValueByPath(TStringBuf path, char delimiter = '.') noexcept;

    void EraseValue(TStringBuf key);
    void EraseValue(size_t index);

    TJsonValue& operator [] (size_t idx);
    TJsonValue& operator [] (const TStringBuf& key);
    const TJsonValue& operator [] (size_t idx) const noexcept;
    const TJsonValue& operator [] (const TStringBuf& key) const noexcept;

    bool GetBoolean() const;
    long long GetInteger() const;
    unsigned long long GetUInteger() const;
    double GetDouble() const;
    const TString& GetString() const;
    const TMap& GetMap() const;
    const TArray& GetArray() const;

    //throwing TJsonException possible
    bool GetBooleanSafe() const;
    long long GetIntegerSafe() const;
    unsigned long long GetUIntegerSafe() const;
    double GetDoubleSafe() const;
    const TString& GetStringSafe() const;
    const TMap& GetMapSafe() const;
    TMap& GetMapSafe();
    const TArray& GetArraySafe() const;
    TArray& GetArraySafe();

    bool GetBooleanSafe(bool defaultValue) const;
    long long GetIntegerSafe(long long defaultValue) const;
    unsigned long long GetUIntegerSafe(unsigned long long defaultValue) const;
    double GetDoubleSafe(double defaultValue) const;
    TString GetStringSafe(const TString& defaultValue) const;

    bool GetBooleanRobust() const noexcept;
    long long GetIntegerRobust() const noexcept;
    unsigned long long GetUIntegerRobust() const noexcept;
    double GetDoubleRobust() const noexcept;
    TString GetStringRobust() const;

    // Exception-free accessors
    bool GetBoolean(bool* value) const noexcept;
    bool GetInteger(long long* value) const noexcept;
    bool GetUInteger(unsigned long long* value) const noexcept;
    bool GetDouble(double* value) const noexcept;
    bool GetMapPointer(const TMap** value) const noexcept;
    bool GetArrayPointer(const TArray** value) const noexcept;

    bool GetString(TString* value) const;
    bool GetMap(TMap* value) const;
    bool GetArray(TArray* value) const;
    bool GetValue(size_t index, TJsonValue* value) const;
    bool GetValue(TStringBuf key, TJsonValue* value) const;
    bool GetValuePointer(size_t index, const TJsonValue** value) const noexcept;
    bool GetValuePointer(TStringBuf key, const TJsonValue** value) const noexcept;
    bool GetValuePointer(TStringBuf key, TJsonValue** value) noexcept;

    // Checking for defined non-null value
    bool IsDefined() const noexcept {
        return Type != JSON_UNDEFINED
            && Type != JSON_NULL;
    }

    bool IsNull() const noexcept;
    bool IsBoolean() const noexcept;
    bool IsDouble() const noexcept;
    bool IsString() const noexcept;
    bool IsMap() const noexcept;
    bool IsArray() const noexcept;

    /// @return true if JSON_INTEGER or (JSON_UINTEGER and Value <= Max<long long>)
    bool IsInteger() const noexcept;

    /// @return true if JSON_UINTEGER or (JSON_INTEGER and Value >= 0)
    bool IsUInteger() const noexcept;

    bool Has(const TStringBuf& key) const noexcept;
    bool Has(size_t key) const noexcept;

    void Scan(IScanCallback& callback);

    /// Non-robust comparison.
    bool operator== (const TJsonValue& rhs) const;

    bool operator!= (const TJsonValue& rhs) const {
        return !(*this == rhs);
    }

    void Swap(TJsonValue& rhs) noexcept;

    static const TJsonValue UNDEFINED;

private:
    EJsonValueType Type;
    union TValueUnion {
        bool Boolean;
        long long Integer;
        unsigned long long UInteger;
        double Double;
        TString* String;
        TMap* Map;
        TArray* Array;
    };
    TValueUnion Value;
    void DoScan(const TString& path, TJsonValue* parent, IScanCallback& callback);
};


inline bool GetBoolean(const TJsonValue &jv, size_t index, bool *value) noexcept {
    return jv[index].GetBoolean(value);
}

inline bool GetInteger(const TJsonValue &jv, size_t index, long long *value) noexcept {
    return jv[index].GetInteger(value);
}

inline bool GetUInteger(const TJsonValue &jv, size_t index, unsigned long long *value) noexcept {
    return jv[index].GetUInteger(value);
}

inline bool GetDouble(const TJsonValue &jv, size_t index, double *value) noexcept {
    return jv[index].GetDouble(value);
}

inline bool GetString(const TJsonValue &jv, size_t index, TString *value) {
    return jv[index].GetString(value);
}

bool GetMapPointer(const TJsonValue& jv, size_t index, const TJsonValue::TMap **value);
bool GetArrayPointer(const TJsonValue& jv, size_t index, const TJsonValue::TArray **value);

inline bool GetBoolean(const TJsonValue &jv, TStringBuf key, bool *value) noexcept {
    return jv[key].GetBoolean(value);
}

inline bool GetInteger(const TJsonValue &jv, TStringBuf key, long long *value) noexcept {
    return jv[key].GetInteger(value);
}

inline bool GetUInteger(const TJsonValue &jv, TStringBuf key, unsigned long long *value) noexcept {
    return jv[key].GetUInteger(value);
}

inline bool GetDouble(const TJsonValue &jv, TStringBuf key, double *value) noexcept {
    return jv[key].GetDouble(value);
}

inline bool GetString(const TJsonValue &jv, TStringBuf key, TString *value) {
    return jv[key].GetString(value);
}

bool GetMapPointer(const TJsonValue& jv, const TStringBuf key, const TJsonValue::TMap **value);
bool GetArrayPointer(const TJsonValue& jv, const TStringBuf key, const TJsonValue::TArray **value);

}
